package snafu.framework.worlds;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JPanel;

import snafu.framework.objects.WorldObject;
import snafu.implementation.states.literals.depth.DepthLiteral;

abstract public class WorldMapPanel extends JPanel implements Observer {
	private WorldMap map;
	private double tileScale;
	private Position selectedPosition;

	public WorldMapPanel(WorldMap map) {
		this.map = map;
		this.selectedPosition = null;

		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseReleased(MouseEvent e) {
				handleMouseClicked(e);
			}
		});
	}

	public void setSelectedPosition(Position selectedPosition) {
		// deselect tile
		if (selectedPosition == null || this.selectedPosition.equals(selectedPosition)) {
			this.selectedPosition = null;
		}
		// select tile
		else {
			this.selectedPosition = selectedPosition;
		}
	}

	public Position getSelectedPosition() {
		return selectedPosition;
	}
	
	public double getTileScale() {
		return tileScale;
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		// tile size
		Point2D mapDimension = map.getDimension();
		tileScale = Math.min(getWidth() / mapDimension.getX(), getHeight() / mapDimension.getY());
		Point2D tileSize = new Point2D.Double(map.getTileSize().getX() * tileScale, map
				.getTileSize().getY() * tileScale);
		
		// panel size
		double panelSize = Math.max(mapDimension.getX() * tileScale, mapDimension.getY() * tileScale);
		setPreferredSize(new Dimension((int) panelSize, (int) panelSize));

		// paint tiles
		for (Tile tile : map.getTiles()) {
			// sort objects by depth
			List<WorldObject> objects = tile.getWorldObjects();
			Collections.sort(objects, new Comparator<WorldObject>() {
				@Override
				public int compare(WorldObject o1, WorldObject o2) {
					Integer depth1 = o1.getState().getLiteral(DepthLiteral.class, 0);
					Integer depth2 = o2.getState().getLiteral(DepthLiteral.class, 0);

					return depth1.compareTo(depth2);
				}
			});

			// draw tile on buffer
			BufferedImage tileBuffer = new BufferedImage((int) tileSize.getX() + 2,
					(int) tileSize.getY() + 2, BufferedImage.TYPE_INT_ARGB);
			// create a copy to avoid concurrent modification exceptions while drawing and killing
			// agents at the same time
			List<WorldObject> copy = new ArrayList<WorldObject>(objects);

			Color color;
			if (selectedPosition != null) {
				// set color for selected/unselected tile
				color = selectedPosition.equals(tile.getPosition()) ? Color.RED
						: Color.BLACK;
			} else {
				color = Color.BLACK;
			}
			
			paintObjects(copy, tileSize, color, tileBuffer.createGraphics());

			// draw buffer
			Point2D position = map.convertPositionToPoint(tile.getPosition());
			int x = (int) (position.getX() * tileScale);
			int y = (int) (position.getY() * tileScale);
			g.drawImage(tileBuffer, x, y, null);
		}
	}

	abstract protected void paintObjects(List<WorldObject> objects, Point2D tileSize,
			Color borderColor, Graphics g);

	@Override
	public void update(Observable o, Object arg) {
		repaint();
	}

	private void handleMouseClicked(MouseEvent e) {

	}
}
